using System;
using System.Collections.Generic;
using System.IO;
using Microsoft.DirectX.DirectInput;
using Microsoft.Xna.Framework.Input;
using System.Xml.Serialization;
using System.Diagnostics;

namespace Soopah.Xna.Input
{
    public class DirectInputGamepad
    {
		
        private ButtonSettings buttonSettings;

        /// <summary>
        /// The currently assigned ButtonSettings File
        /// </summary>
        public ButtonSettings bSettings
        {
            get { return buttonSettings; }
            set 
            {
                if (value != null)
                    SettingsLoaded = true;
                buttonSettings = value;
            }
        }
        
        /// <summary>
        /// The name of the GamePad as it appears in Windows
        /// </summary>
        public string PadName
        {
            get { return device.DeviceInformation.ProductName; }
        }

        bool SettingsLoaded=false;

        /// <summary>
        /// Read a settings file from a path
        /// </summary>
        /// <param name="Path">Path and filename for the settings file</param>
        public void LoadSettings(string Path)
        {
            if (File.Exists(Path))
            {
                try
                {
                    XmlSerializer xml = new XmlSerializer(typeof(ButtonSettings));
                    FileStream fs = File.OpenRead(Path);
                    bSettings = (ButtonSettings)xml.Deserialize(fs);
                    fs.Close();
                    SettingsLoaded = true;
                }
                catch (Exception ex)
                {
                    throw new FieldAccessException("Error Reading File:" + Path + "\n" + ex.Message);
                }
            }
        }

        /// <summary>
        /// Save the currently applied ButtonSettings to a file
        /// </summary>
        /// <param name="Path"></param>
        public void SaveSettings(string Path)
        {
            try
            {
                XmlSerializer xml = new XmlSerializer(typeof(ButtonSettings));
                FileStream fs = File.Create(Path);
                xml.Serialize(fs, bSettings);
                fs.Close();
            }
            catch (Exception ex)
            {
                throw new FieldAccessException("Error Writing File:" + Path + "\n" + ex.Message);
            }
        }
        

		protected Device device;
		public Device Device
		{
			get { return device; }
		}

        
		internal DirectInputGamepad(Guid gamepadInstanceGuid)
		{
			device = new Device(gamepadInstanceGuid);
			device.SetDataFormat(DeviceDataFormat.Joystick);
            foreach (DeviceObjectInstance di in device.Objects)
            {
                if ((di.ObjectId & (int)DeviceObjectTypeFlags.Axis) != 0)
                {
                    device.Properties.SetRange(ParameterHow.ById, di.ObjectId, new InputRange(0, 512));
                    device.Properties.SetDeadZone(ParameterHow.ById, di.ObjectId, 1500);
                }
            }

			device.Acquire();	
		
		}

        /// <summary>
        /// Gets the collection of Analog inputs (e.g. Thumbsticks, Triggers)
        /// </summary>
		public DirectInputThumbSticks ThumbSticks
		{
			get 
            {
                if (!SettingsLoaded)
                    return new DirectInputThumbSticks(device);
                else
                    return new DirectInputThumbSticks(device, bSettings);
            }
		}

        /// <summary>
        /// Returns the Dpad as a collection of 4 XNA ButtonStates
        /// </summary>
		public DirectInputDPad DPad
		{
			get
			{
                try
                {
                    JoystickState t = device.CurrentJoystickState;
                    return new DirectInputDPad(t.GetPointOfView()[0]);	// note that there could be a total of 4 DPads on the PC
                }
                catch (Exception Ex)
                {
                    //TODO Handle Input Lost
                    Trace.WriteLine(Ex.Message);
                    Trace.WriteLine(Ex.StackTrace);
                    return new DirectInputDPad(-1);
                }
			}
		}

        /// <summary>
        /// Returns a collection of XNA ButtonStates.
        /// </summary>
		public DirectInputButtons Buttons
		{
			get 
            {
                if (!SettingsLoaded)
                    return new DirectInputButtons(device);
                else
                    return new DirectInputButtons(device, bSettings);
            }
		}

        /// <summary>
        /// Polls the Device
        /// </summary>
        public void Poll()
        {
            device.Poll();
        }

        #region Diagnostics
        public string DiagnosticsThumbSticks
		{
			get
			{
				return
					"X" + Math.Round(ThumbSticks.Left.X, 4) +
					" Y" + Math.Round(ThumbSticks.Left.Y, 4) +
					" X" + Math.Round(ThumbSticks.Right.X, 4) +
					" Y" + Math.Round(ThumbSticks.Right.Y, 4);
			}
		}

		public string DiagnosticsRawGamepadData
		{
			get
			{
				return
					"(X:" + Device.CurrentJoystickState.X + "    " +
                    " Y:" + Device.CurrentJoystickState.Y + "    " +
                    " Z:" + Device.CurrentJoystickState.Z + ")\n" +
                    "(Rx:" + Device.CurrentJoystickState.Rx + "   " +
                    " Ry:" + Device.CurrentJoystickState.Ry + "   " +
                    " Rz:" + Device.CurrentJoystickState.Rz + ")\n" +
                    "(VX:" + Device.CurrentJoystickState.VX + "   " +
                    " VY:" + Device.CurrentJoystickState.VY + "   " +
                    " VZ:" + Device.CurrentJoystickState.VZ + ")\n" +
                    "(AX:" + Device.CurrentJoystickState.AX + "   " +
                    " AY:" + Device.CurrentJoystickState.AY + "   " +
                    " AZ:" + Device.CurrentJoystickState.AZ + ")\n" +
                    "(FX:" + Device.CurrentJoystickState.FX + "   " +
                    " FY:" + Device.CurrentJoystickState.FY + "   " +
                    " FZ:" + Device.CurrentJoystickState.FZ + ")\n" +
                    "(VRx:" + Device.CurrentJoystickState.VRx+ "  " +
                    " VRy:" + Device.CurrentJoystickState.VRy + "  " +
                    " VRz:" + Device.CurrentJoystickState.VRz + ")\n" +
                    "(FRx:" + Device.CurrentJoystickState.FRx+ "  " +
                    " FRy:" + Device.CurrentJoystickState.FRy + "  " +
                    " FRz:" + Device.CurrentJoystickState.FRy + ")\n" +
                    "(ARx:" + Device.CurrentJoystickState.ARx + "  " +
                    " ARy:" + Device.CurrentJoystickState.ARy + "  " +
                    " ARz:" + Device.CurrentJoystickState.ARz + ")\n" +
                    "Throttle:" + ((Device.CurrentJoystickState.GetSlider()[0]-32767.5f)/32767.5f).ToString() + "  "+
                    " DPad:" + Device.CurrentJoystickState.GetPointOfView()[0];
			}
		}

		public string DiagnosticsButtons
		{
			get
			{
				System.Text.StringBuilder sb = new System.Text.StringBuilder();

				int i = 0;
				foreach (ButtonState bs in Buttons.List)
				{
					sb.Append(i+1);
					sb.Append("=");
					sb.Append((bs == ButtonState.Pressed ? "1" : "0"));
					sb.Append(" ");
                    if (i == 8 || i == 16)
                        sb.Append("\r\n");
					i++;
				}

				return sb.ToString();
			}
		}
		#endregion
	}

    public class DirectInputGamepadManager
    {
        protected static List<DirectInputGamepad> gamepads;
        public static List<DirectInputGamepad> Gamepads
        {
            get
            {
                if (gamepads == null)
                    ReloadGamepads();

                return gamepads;
            }
        }


        /// <summary>
        /// Normally for internal use only; call if user has attached new Gamepads,
        /// or detached Gamepads you want discarded
        /// Otherwise, loaded once on first Gamepad request and does not reflect changes in gamepad attachment
        /// TODO: Do this better
        /// </summary>
        public static void ReloadGamepads()
        {
            // gamepads generally misidentified as Joysticks in DirectInput... get both
            DeviceList gamepadInstanceList = Manager.GetDevices(DeviceType.Gamepad, EnumDevicesFlags.AttachedOnly);
            DeviceList joystickInstanceList = Manager.GetDevices(DeviceType.Joystick, EnumDevicesFlags.AttachedOnly);

            gamepads = new List<DirectInputGamepad>(gamepadInstanceList.Count + joystickInstanceList.Count);

            foreach (DeviceInstance deviceInstance in gamepadInstanceList)
            {
                DirectInputGamepad gamepad = new DirectInputGamepad(deviceInstance.InstanceGuid);
                gamepads.Add(gamepad);
            }
            foreach (DeviceInstance deviceInstance in joystickInstanceList)
            {
                DirectInputGamepad gamepad = new DirectInputGamepad(deviceInstance.InstanceGuid);
                gamepads.Add(gamepad);
            }
        }

        /// <summary>
        /// Polls all of the devices in the collection
        /// </summary>
        public static void PollDevices()
        {
            for (int i = 0; i < gamepads.Count; i++)
            {
                gamepads[i].Poll();
            }
        }


    }
}
